#pragma once


namespace VirtualPedal
{
	using namespace System::Collections;
	using namespace System::Collections::Generic;
	using namespace System::Runtime::InteropServices;
	using namespace System::Threading;

	public ref class AudioSource sealed : public AudioDeviceBase
	{
		List<DeviceDescriptor^>^ m_Devices;
		CliComPtr<IAudioClient>^ m_InputClient;
		CliComPtr<IAudioClock>^ m_InputClock;
		CliComPtr<IAudioCaptureClient>^ m_InputCaptureClient;
		UInt64 m_InputFrequency;
		UInt64 m_CurrentPosition;
		HANDLE m_hEvent;
		bool m_bInLoop;
		bool m_bReadingLoopFlag;
		Thread^ m_ReadingInputThread;

		AudioPort^ m_OutPort;
		AudioStreamDescriptor^ m_StreamDesc;

		List<PedalBase^>^ m_Pedals;

	public:
		virtual property List<DeviceDescriptor^>^ Devices {
			virtual List<DeviceDescriptor^>^ get() override { return m_Devices; }
		}

		AudioPort^ GetDataPort()
		{
			return m_OutPort;
		}

		property Int32 NumChannels
		{
			Int32 get() { return m_StreamDesc->NumChannels; }
		}

		virtual property bool IsOpened
		{
			virtual bool get() override { return m_bInLoop; }
		}

	public:

		AudioSource()
		{
			m_InputClient = gcnew CliComPtr<IAudioClient>();
			m_InputClock = gcnew CliComPtr<IAudioClock>();
			m_InputCaptureClient = gcnew CliComPtr<IAudioCaptureClient>();
			m_InputFrequency = 0;
			m_CurrentPosition = 0;
			m_hEvent = nullptr;
			m_bInLoop = false;
			m_bReadingLoopFlag = false;
			m_Pedals = gcnew List<PedalBase^>();
		}

		void AddPedal(PedalBase^ pedal)
		{
			m_Pedals->Add(pedal);
		}

		virtual void Init() override
		{
			m_Devices = gcnew List<DeviceDescriptor^>();

			HRESULT hr = S_OK;
			ComPtr<IMMDeviceEnumerator> pEnumerator;
			ComPtr<IMMDeviceCollection> pCollection;
			ComPtr<IMMDevice> pEndPoint;
			ComPtr<IPropertyStore> pProps;
			
			LPWSTR pwszID = NULL;
			UINT count = 0;

			hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, 
				__uuidof(IMMDeviceEnumerator), (void**)pEnumerator.ReleaseAndGetAddressOf());

			ThrowOnError(hr);

			hr = pEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, pCollection.ReleaseAndGetAddressOf());

			ThrowOnError(hr);

			hr = pCollection->GetCount(&count);

			ThrowOnError(hr);

			DeviceDescriptor^ desc;

			for(UINT i=0; i<count; i++)
			{
				hr = pCollection->Item(i, pEndPoint.ReleaseAndGetAddressOf());

				ThrowOnError(hr);

				desc = gcnew DeviceDescriptor();

				hr = pEndPoint->GetId(&pwszID);

				if(pwszID)
				{
					desc->Name = gcnew String(pwszID);
					CoTaskMemFree(pwszID);
					pwszID = NULL;
				}

				ThrowOnError(hr);

				hr = pEndPoint->OpenPropertyStore(STGM_READ, pProps.ReleaseAndGetAddressOf());

				ThrowOnError(hr);

				PROPVARIANT varName;
				PropVariantInit(&varName);

				hr = pProps->GetValue(PKEY_Device_FriendlyName, &varName);

				ThrowOnError(hr);

				desc->FriendlyName = gcnew String(varName.pwszVal);

				PropVariantClear(&varName);

				m_Devices->Add(desc);
			}
		}

		virtual void Open(DeviceDescriptor^ desc) override
		{
			if(m_bInLoop)
				return;

			HRESULT hr = S_OK;
			ComPtr<IMMDeviceEnumerator> pEnumerator;
			ComPtr<IMMDeviceCollection> pCollection;
			ComPtr<IMMDevice> pEndPoint;
			ComPtr<IPropertyStore> pProps;

			LPWSTR pwszID = NULL;
			UINT count = 0;

			hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, 
				__uuidof(IMMDeviceEnumerator), (void**)pEnumerator.ReleaseAndGetAddressOf());

			ThrowOnError(hr);

			hr = pEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, pCollection.ReleaseAndGetAddressOf());

			ThrowOnError(hr);

			hr = pCollection->GetCount(&count);

			ThrowOnError(hr);

			for(UINT i=0; i<count; i++)
			{
				hr = pCollection->Item(i, pEndPoint.ReleaseAndGetAddressOf());

				ThrowOnError(hr);

				hr = pEndPoint->GetId(&pwszID);

				if(pwszID)
				{
					String^ strId = gcnew String(pwszID);
					CoTaskMemFree(pwszID);
					pwszID = NULL;

					if(strId == desc->Name)
					{
						break;
					}
				}
				ThrowOnError(hr);
			}

			hr = pEndPoint->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)m_InputClient->Ptr().ReleaseAndGetAddressOf());
			ThrowOnError(hr);


			WAVEFORMATEXTENSIBLE *closeFormat = NULL;
			REFERENCE_TIME bufferTime100ns = 10000000;

			hr = m_InputClient->Ptr()->GetMixFormat((WAVEFORMATEX**)&closeFormat);
			ThrowOnError(hr);

			m_StreamDesc = gcnew AudioStreamDescriptor(closeFormat);
			m_OutPort = gcnew AudioPort(m_StreamDesc);

			hr = m_InputClient->Ptr()->Initialize(
				AUDCLNT_SHAREMODE_SHARED,
				AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED | AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED,
				bufferTime100ns, 0, (WAVEFORMATEX*)&closeFormat->Format, NULL);
			//if(closeFormat)
			//{
			//	CoTaskMemFree(&closeFormat);
			//}
			ThrowOnError(hr);

			hr = m_InputClient->Ptr()->GetService(__uuidof(IAudioCaptureClient), (void**)m_InputCaptureClient->Ptr().ReleaseAndGetAddressOf());
			ThrowOnError(hr);

			hr = m_InputClient->Ptr()->GetService(__uuidof(IAudioClock), (void**)m_InputClock->Ptr().ReleaseAndGetAddressOf());
			ThrowOnError(hr);

			UINT64 freq = 0;
			hr = m_InputClock->Ptr()->GetFrequency(&freq);
			ThrowOnError(hr);
			
			m_InputFrequency = freq;

			m_bInLoop = true;

			m_hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
			
			if(m_hEvent == NULL)
			{
				throw gcnew VPException(::GetLastError(), L"Failed to CreateEvent.");
			}

			hr = m_InputClient->Ptr()->SetEventHandle(m_hEvent);
			ThrowOnError(hr);

			UINT32 numBufferFrames = 0;
			hr = m_InputClient->Ptr()->GetBufferSize(&numBufferFrames);
			ThrowOnError(hr);

			m_bReadingLoopFlag = true;
			ThreadStart^ ts = gcnew ThreadStart(this, &AudioSource::AudioCaptureProc);
			m_ReadingInputThread = gcnew Thread(ts);
			m_ReadingInputThread->Start();

			hr = m_InputClient->Ptr()->Start();
			ThrowOnError(hr);
		}

		virtual void Close() override
		{
			if(!m_bInLoop || m_InputClient->Ptr() == nullptr || m_InputCaptureClient->Ptr() == nullptr)
			{
				return;
			}

			m_bReadingLoopFlag = false;

			const DWORD loopTimeOut = 2000;
			
			DWORD ret = ::WaitForSingleObject(m_hEvent, loopTimeOut);

			if(ret != WAIT_OBJECT_0)
			{
				m_ReadingInputThread->Abort();
			}

			::CloseHandle(m_hEvent);
			m_hEvent = NULL;

			HRESULT hr;
			hr = m_InputClient->Ptr()->Stop();

			m_InputClock->Ptr() = nullptr;
			m_InputCaptureClient->Ptr() = nullptr;
			m_InputClient->Ptr() = nullptr;
		}

	private:

		void AudioCaptureProc()
		{
			const DWORD DEVICE_TIME_OUT_TIME = 2000;
			HRESULT hr;

			try
			{
				if(!m_bInLoop)
				{
					return;
				}

				while(m_bReadingLoopFlag)
				{
					DWORD ret = ::WaitForSingleObject(m_hEvent, DEVICE_TIME_OUT_TIME);
					if(ret != WAIT_OBJECT_0)
					{
						m_InputClient->Ptr()->Stop();
						hr = ERROR_TIMEOUT;
						ThrowOnError(hr);
					}

					DWORD flags = 0;
					BYTE* pData = nullptr;
					UINT32 packetLength = 0;
					UINT32 numFramesAvaliable = 0;
					hr = m_InputCaptureClient->Ptr()->GetNextPacketSize(&packetLength);
					ThrowOnError(hr);

					std::vector<BYTE> tmpBuffer0(m_OutPort->DataBuffer->BufferSize);
					std::vector<BYTE> tmpBuffer1(m_OutPort->DataBuffer->BufferSize);
					FLOAT gain = powf(10, Gain);

					while(packetLength != 0)
					{
						hr = m_InputCaptureClient->Ptr()->GetBuffer(
							&pData, &numFramesAvaliable, &flags, NULL, NULL);
						ThrowOnError(hr);

						if(flags & AUDCLNT_BUFFERFLAGS_SILENT)
						{
							pData = NULL;
						}

						//Copy data here
						UINT32 nBytesPerSample = m_StreamDesc->BitsPerSample / 8;

						UINT32 numData = numFramesAvaliable * m_StreamDesc->NumChannels;
						memcpy(&tmpBuffer0[0], pData, numData * nBytesPerSample);

						if(m_OutPort->Descriptor->ValidBitsPerSample == 32)
						{
							FLOAT* pSrc = (FLOAT*)&tmpBuffer0[0];
							for(UINT32 frame = 0; frame < numFramesAvaliable; frame++)
							{
								for(WORD chnn = 0; chnn < m_StreamDesc->NumChannels; chnn++)
								{
									//Temporary fix 
									//*pSrc = *pSrc * gain;	
									if(chnn & 1)
										*pSrc = *(pSrc - (chnn & 1));
									else
										*pSrc = *pSrc * gain;
									pSrc++;
								}
							}
						}
						else
						{
							
						}

						std::vector<BYTE>& tmpSrc = tmpBuffer0;
						std::vector<BYTE>& tmpDst = tmpBuffer1;
						for(int i=0; i<m_Pedals->Count; i++)
						{
							if(!m_Pedals[i]->IsEnabled)
								continue;
							m_Pedals[i]->NumChannels = m_StreamDesc->NumChannels;
							m_Pedals[i]->SamplingRate = m_StreamDesc->SampleFrequency;
							m_Pedals[i]->Process((FLOAT*)&tmpSrc[0], (FLOAT*)&tmpDst[0], numData);
							std::vector<BYTE>& tmp = tmpSrc;
							tmpSrc = tmpDst;
							tmpDst = tmp;
						}

						m_OutPort->DataBuffer->Write(&tmpSrc[0], numData * nBytesPerSample);

						hr = m_InputCaptureClient->Ptr()->ReleaseBuffer(numFramesAvaliable);
						ThrowOnError(hr);

						hr = m_InputCaptureClient->Ptr()->GetNextPacketSize(&packetLength);
						ThrowOnError(hr);
					}
				}
			}
			catch (System::Threading::ThreadAbortException^)
			{

			}
			finally
			{
				m_bInLoop = false;
			}
		}

	};
}